// call 的实现

Function.prototype.myCall = function(context) {
  console.log('this2', this) //[Function: Person]
  console.log('context', context) // {}

  // 1.判断this是否为函数   ---- 这里的this指的是调用myCall方法的函数, 而不是myCall方法的原型链上的函数
  if (typeof this!== 'function') {
    throw new TypeError('Error: this is not a function');
  }

  // 2.判断传入的上下文是否存在，不存在设为window
  context = context || window;

  // 3.处理传⼊的参数，截取第⼀个参数后的所有参数。
  let args = [...arguments].slice(1),
      result = null;

  // 4. 将函数作为上下文对象的属性，并将传入的参数传给函数。
  context.fn = this;

  // 5. 使⽤上下⽂对象来调⽤这个⽅法，并保存返回结果。
  result = context.fn(...args);

  // 6. 删除上下⽂对象中的fn属性，避免污染全局作用域。
  delete context.fn;

  // 7. 返回函数的返回值。
  return result;
}

function Person(name) {
  this.name = name;
  console.log(this.name);
}


// 上述的处理简单来说就是，构造方法Person调用myCall，此时this指向方法Person，我们想让this指向新建的对象上，就需要让新建的对象执行构造方法Person，通过context.fn = this;把构造方法this即Person作为新建对象context的一个属性fn，然后 context.fn(...args)去执行它。
// const p1 = new Person('xiao')
console.log('this1', this) // {} 
const res = Person.myCall(this)
console.log('this3', this) // this3 { name: undefined }




// apply 的实现
Function.prototype.myApply = function(context) {
  // 1.判断this是否为函数
  if (typeof this!== 'function') {
    throw new TypeError('Error: this is not a function');
  }

  // 2.判断传入的上下文是否存在，不存在设为window
  context = context || window;

  // 3.处理传⼊的参数，截取第⼀个参数后的所有参数。 --- apply的第二个参数是数组
  let args = arguments[1]? [...arguments[1]] : [];

  // 4. 将函数作为上下文对象的属性，并将传入的参数传给函数。
  context.fn = this;

  // 5. 使⽤上下⽂对象来调⽤这个⽅法，并保存返回结果。
  let result = context.fn(...args);

  // 6. 删除上下⽂对象中的fn属性，避免污染全局作用域。
  delete context.fn;

  // 7. 返回函数的返回值。
  return result;
}



// bind 的实现
Function.prototype.myBind = function(context) {
  // 1.判断this是否为函数
  if (typeof this !== 'function') {
    throw new TypeError('Error: this is not a function');
  }

  // 2.处理传⼊的参数，截取第⼀个参数后的所有参数。 --- bind的第二个参数是数组
  let args = [...arguments].slice(1),
      fn = this;

  // 3. 函数内部使⽤ apply 来绑定函数调⽤，需要判断函数作为构造函数的情况，这个时候需要传⼊当前函数的 this 给 apply 调⽤，其余情况都传⼊指定的上下⽂对象。
  return function Fn() {
    return fn.apply(
      this instanceof Fn ? this : context,
      args.concat(...arguments)
    );
  }
}
